﻿using LightCAD.MathLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.Core
{
    public enum TriCoverState
    {
        Front,
        Splite,
        Back,
        Coplanar
    }
    public static class Projection
    {
        public static ListEx<Curve2d> GetProjectionResult(GeometryData geo, Vector3 prjDir = null)
        {
            if (prjDir == null)
                prjDir = new Vector3(0, 0, -1);

            var faces = GetFaces(geo);
            faces = FilterFaces(faces, prjDir);

            var clipper = new ClipperWrapper();
            var remainShapeList = new List<Polygon>();
            //for (int i = 0; i < faces.Count; i++)
            //{
            //    var face1 = faces[i];
            //    var coverFaces = new List<Polygon>();
            //    for (int j = 0; j < faces.Count; j++)
            //    {
            //        if (i == j)
            //            continue;
            //        var face2 = faces[j];

            //        if (!face1.Box3.IntersectsBox(face2.Box3))
            //            continue;

            //        var remainShapes = ComputeTriAdjTriCover(face1, face2, out var state);
            //        if (remainShapes != null)
            //        {
            //            coverFaces.Add(remainShapes);
            //        }
            //    }
            //    clipper.Set(new List<List<Vector2>>() { face1.ToPoint2d() });
            //    clipper.SetClips(coverFaces.Select(p => p.ToPoint2d()).ToList());
            //    var shapes = clipper.GetShapes(0.5);

            //    for (int j = 0; j < shapes.Count; j++)
            //    {
            //        var cs = shapes[j];
            //        if (cs.Points == null || cs.Points.Count == 0)
            //            continue;

            //        var polygon = new Polygon(cs.Points.Select(p => p.ToVector3()).ToListEx());
            //        remainShapeList.Add(polygon);
            //    }
            //}
            remainShapeList = faces;

            //根据三角片的法向量进行分组
            var planeFaceGrps = new Dictionary<Plane, List<Polygon>>();
            for (int i = 0; i < remainShapeList.Count; i++)
            {
                var plane = remainShapeList[i].Plane;
                var key = planeFaceGrps.Keys.FirstOrDefault(p => Utils.PlaneEQ(p, plane, 1E-05, 5));
                //var key = planeFaceGrps.Keys.FirstOrDefault(p => Utils.PlaneEQ(p, plane,1E-05, 5) || Utils.PlaneEQ(p, plane.Clone().Negate(), 1E-05, 5));
                if (key == null)
                {
                    key = plane;
                    planeFaceGrps.Add(key, new List<Polygon>());
                }

                //key = planeFaceGrps.Keys.FirstOrDefault();
                planeFaceGrps[key].Add(remainShapeList[i]);
            }
            var pointGrps = new ListEx<ListEx<Vector2>>();
            foreach (var kvp in planeFaceGrps)
            {
                clipper.Set(kvp.Value.Select(P => P.ToPoint2d()).ToList());
                clipper.Execute(ClipType.ctUnion);

                var clipperShapes = clipper.GetShapes(1e-2);
                if (clipperShapes == null || clipperShapes.Count == 0)
                    continue;

                for (int i = 0; i < clipperShapes.Count; i++)
                {
                    var cs = clipperShapes[i];
                    if (cs.Points == null || cs.Points.Count == 0)
                        continue;

                    pointGrps.Push(cs.Points.ToListEx());
                }
            }
            var curves = new ListEx<Curve2d>();
            foreach (var loops in pointGrps)
            {
                int count = loops.Count;
                var isClose = Utils.Vec2EQ(loops[0], loops[count - 1]);
                if (isClose)
                    count -= 1;

                for (int i = 0; i < count; i++)
                {
                    int nxi = (i + 1) % count;
                    var line = new Line2d(loops[i].Clone(), loops[nxi].Clone());
                    curves.Add(line);
                }
            }
            return curves;
        }

        private static List<Polygon> GetFaces(GeometryData geo)
        {
            var faces = new List<Polygon>();
            var verteics = geo.Verteics;
            var indics = geo.Indics;
            for (int i = 0; i < indics.Length; i += 3)
            {
                var i1 = i + 1;
                var i2 = i + 2;
                var p0 = new Vector3(verteics[indics[i] * 3], verteics[indics[i] * 3 + 1], verteics[indics[i] * 3 + 2]);
                var p1 = new Vector3(verteics[indics[i1] * 3], verteics[indics[i1] * 3 + 1], verteics[indics[i1] * 3 + 2]);
                var p2 = new Vector3(verteics[indics[i2] * 3], verteics[indics[i2] * 3 + 1], verteics[indics[i2] * 3 + 2]);
                faces.Add(new Polygon(p0, p1, p2));
            }
            return faces;
        }

        private static List<Polygon> FilterFaces(List<Polygon> faces, Vector3 prjDir)
        {
            //return faces.FindAll(tri => !tri.IsBack(prjDir) && tri.HaveArea);
            return faces.FindAll(tri => !tri.IsBack(prjDir) && tri.HaveArea);
        }

        /// <summary>
        /// 相邻三角片遮挡运算
        /// </summary>
        /// <param name="tri"></param>
        /// <param name="adjTri"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public static Polygon ComputeTriAdjTriCover(Polygon tri, Polygon adjTri, out TriCoverState state)
        {
            //如果共面直接返回相邻三角片
            if (Utils.Vec3EQ(tri.Plane.Normal, adjTri.Plane.Normal, 1e-3) &&
                Utils.IsEqual(tri.Plane.Constant, adjTri.Plane.Constant, 5))
            {
                Polygon upPoly = null;
                state = TriCoverState.Coplanar;
                try
                {
                    upPoly = new Polygon(adjTri.Points);
                }
                catch (Exception err)
                {
                    console.error(err);
                }
                return upPoly;
            }
            return tri.SplitTriangle(adjTri, out state);

        }
    }
}
